---
sidebar_position: 1
---

# Для программ на C

:::info

Данная статья основана на материале статьи ["Dynamic Array in C"](https://www.scaler.com/topics/c/dynamic-array-in-c/).

:::

**Динамические массивы** - очень полезные структуры данных. Они могут быть инициализированы с переменным размером во время выполнения. Этот размер может быть изменен позже в программе, чтобы расширить (или) уменьшить массив. В отличие от массивов фиксированного размера и массивов переменной длины, массивы с динамическим размером размещаются в куче.

В этой статье рассматриваются:

- построение динамических массивов;
- использование низкоуровневых функций, таких как `malloc`, `free`, `realloc` для реализации массивов динамического размера.

## Функция `malloc`

Мы можем использовать несколько функций `C`, таких как `malloc`, `free`, `calloc`, `realloc`, `reallocarray`, для реализации массивов динамического размера.

Вызов функции `malloc` эквивалентен запросу операционной системы выделить `n` байт. Если выделение памяти прошло успешно, `malloc` возвращает указатель на блок памяти. В противном случае он возвращает `NULL`. `malloc` ссылается на "выделение памяти".

Сигнатура функции:

```c
void *malloc(size_t size);
```

Пример использования `malloc`:

```c
#include <stdio.h>
#include <stdlib.h>

int main() {
    int n = 10;
    int * p = malloc(n);
    if (p == NULL) {
      printf("Unable to allocate memory :(\n");
      return -1;
    }
    printf("Allocated %d bytes of memory\n", n);
    return 0;
}
```

В приведенном выше фрагменте мы используем `malloc` для создания `n` байт памяти и назначения ее указателю `p`. Компиляция его с приведенными ниже флагами немедленно выдает ошибку. Очевидно, что было сделано что-то не так. Мы не освобождали память.

```bash
❯ gcc -fsanitize=leak malloc-snippet.c && ./a.out
Allocated 10 bytes of memory

=================================================================
==46405==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 10 byte(s) in 1 object(s) allocated from:
    #0 0x7f011904a5d1 in __interceptor_malloc ../../../../src/libsanitizer/lsan/lsan_interceptors.cpp:56
    #1 0x56087c6bf1a8 in main (/home/appaji/dynamic-size-arrays-in-c/a.out+0x11a8)
    #2 0x7f0118e76564 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x28564)

SUMMARY: LeakSanitizer: 10 byte(s) leaked in 1 allocation(s).

```

## Доступ к элементам массива

В C добавление `1` к указателю `p` увеличивает адрес указателя на `sizeof(pType)`, где `pType` - это тип данных, на который указывает `p`. Используя это, мы можем получить доступ к его элементам, добавив `i` к базовому указателю. Давайте обсудим это на примере (посмотрите на изображение ниже).

- `a` - это массив с плавающей запятой. Ясно, что `a[0]` является базовым указателем для этого массива.
- `ptr` также указывает на `&a`, т.е. базовый указатель.
- `ptr++` подразумевает добавление байтов `sizeof(float)`, т.е. `4` байта к `ptr`.
- Итак, теперь `ptr` указывает на следующий элемент массива, т.е. `a[1]`. Аналогично, добавив соответствующий `i` в `ptr`, мы можем получить доступ к любому элементу в этом массиве, например: `*(ptr + i) = a[i]`.

![Доступ к элементам динамического массива](images/accessing-array-elements-in-c.png)

## Функция `calloc`

Функция `calloc` выделяет непрерывный блок памяти заданного размера и инициализирует каждый блок нулевым значением. В то время как `malloc` выделяет один блок памяти, но значение в указанном местоположении является случайным ("мусор"). `calloc` выделяет память и обнуляет выделенные блоки. calloc относится к "непрерывному распределению.

Сигнатура функции:

```c
void *calloc(size_t nmemb, size_t size);
```

## `malloc` vs `calloc`

- `calloc` принимает два аргумента, тогда как `malloc` принимает один. `nmemb` представляет количество блоков памяти. `size` представляе*т размер каждого блока*. Это больше подходит для выделения памяти для массивов.
- `malloc` выделяет память сразу, в одном блоке, в то время как `calloc` выделяет память в нескольких блоках, которые являются смежными.

Примечание: нулевое значение означает не просто `0`. Если мы выделяем массив структур, `calloc` присваивает `NULL` строкам, `0` - целым числам, числам с плавающей запятой и т.д.

## Функция `free`

Функция `free` освобождает динамическую память. Вызов `free(p)` непосредственно перед возвратом в приведенном выше фрагменте кода предотвратил бы ошибку.

:::danger

Функция `free` ДОЛЖНА быть вызвана явно после использования динамической памяти, независимо от того, какая функция используется для его создания (`malloc`, `calloc` и т.д.).

:::

## Пример работы с динамическим массивом

В приведенном ниже коде продемонстрированы основные механизмы работы с динамическим массивами.

```c
#include <stdio.h>
#include <time.h>                                  // time
#include <stdlib.h>                                // srand, rand
#include <locale.h>


// Вывод динамического массива.
void printDynamicArray(int n, const int *arr) {
    for (int i = 0; i < n; i++) {
        printf("%d ", *(arr + i)); // разыменование указателя
    }
}

// Генерировать случайного целого числа из диапозона [lower, upper].
int iRandom(int lower, int upper) {
    // https://www.geeksforgeeks.org/generating-random-number-range-c/
    int num = (rand() % (upper - lower + 1)) + lower;
    return num;
}


int main() {
    setlocale(LC_ALL, "Russian");

    // это значение можно считать с клавиатуры
    int n = 13;
    printf("Размер массива: %d\n", n);

    // cоздаем достаточно места для целых чисел 'n'
    // https://www.scaler.com/topics/c/dynamic-array-in-c/
    int *arr = calloc(n, sizeof(int));
    if (arr == NULL) {
        printf("Не удается выделить память\n");
        return EXIT_FAILURE;
    }

    // использовать текущее время в качестве
    // начальное значение для генератора случайных чисел
    srand(time(0));

    // сброс первого числа, чтобы не
    // повторялось, пока srand не изменяется
    rand();

    // цикл инициализации массива
    for (int i = 0; i < n; i++) {
        // целая случайная величина
        arr[i] = iRandom(1, 9);
    }
    printf("Заданный массив: ");
    printDynamicArray(n, arr);
    printf("\n");

    printf("Удаление первого элемента, т.е., arr[0] = %d.\n", arr[0]);
    for (int i = 1; i < n; i++) {
        arr[i - 1] = arr[i];
    }

    // перераспределение памяти
    arr = realloc(arr, (n - 1) * sizeof(int));

    printf("Модифицированный массив: ");
    printDynamicArray(n, arr);

    // освобождаем выделенную память
    free(arr);
    return EXIT_SUCCESS;
}
```

Вывод программы будет таким:

```bash
Размер массива: 13
Заданный массив: 4 3 1 9 3 7 4 9 7 1 7 4 8 
Удаление первого элемента, т.е., arr[0] = 4.
Модифицированный массив: 3 1 9 3 7 4 9 7 1 7 4 8 8 
```

Давайте рассмотрим приведенный пример более подробно:

- `calloc` создала память для `n` целых чисел, где `n` - это ввод может получаться путем вводы с клавиатуры.
- `arr` содержит ячейку памяти созданной памяти. Мы проверяем, прошло ли распределение успешно, и выходим из программы, если это не так.
- Для примера в цикле мы перемещаем массив на один элемент влево, чтобы мы могли удалить последний элемент. Обратите внимание, что мы используем синтаксис, подобный массиву, для ссылки на элементы массива.
- Вызов `realloc` изменил размер памяти на `(n - 1)` байт.
- С помощью вызова `free` мы освободили память и завершили программу с возвращением `0`.
